<?php
mb_internal_encoding('utf-8');

abstract class Str {
	const ASCII   = 1;
	const UNICODE = 2;

	const LEFT       = 1;
	const RIGHT      = 2;
	const BOTH       = 3;
	const LEFT_WITH  = 4;
	const RIGHT_WITH = 5;

	const UP          = 1;
	const DOWN        = 2;
	const UP_FORCED   = 3;
	const DOWN_FORCED = 4;
	const INVERT      = 5;
	const RANDOM      = 6;

	const ALL   = 1;
	const FIRST = 2;
	const TITLE = 3;

	const BOOL  = 3;
	const COUNT = 4;

	const SLASHES     = 1;
	const SLASHES_ADD = 2;
	const HTML        = 3;
	const HTML_DECODE = 4;
	const TAGS        = 5;

	const ENCODE = 1;
	const DECODE = 2;

	// Case-Sensitive / Case-Insensitive
	const CI = false;
	const CS = true;

	/**
	 * Get information about using encoding
	 * @return bool
	 */
	public static function isMultyByte () {
		return true;	
	}

	/**
	 * Taking string by part. Examples:
	 *     Str::take('c', 'abcde'); // returns 'de'
	 *     Str::take('c', 'AbCdE', Self::LEFT_WITH, Str::CI); // returns 'AbC'
	 * @param string $what
	 * @param string $where
	 * @param int $type
	 * @param bool $caseSensitive
	 * @return string|null
	 */
	public static function take ($what, $where, $type = self::RIGHT, $caseSensitive = self::CS) {
		$pos = $caseSensitive ? strpos($where, $what) :
			strpos(self::changeCase($where), self::changeCase($what));

		if ($pos === false) {
			return null;
		}

		switch ($type) {
			case self::LEFT:
				return substr($where, 0, $pos);

			case self::RIGHT_WITH:
				return substr($where, $pos);

			case self::LEFT_WITH:
				return substr($where, 0, $pos + strlen($what));

			case self::RIGHT:
			default:
				return substr($where, $pos + strlen($what));
		}
	}

	/**
	 * Filter $what string/array from unsafe symbols
	 * @param array|string $what
	 * @param int $mode
	 * @return array|string
	 */
	public static function filter ($what, $mode) {
		if (is_array($what)) {
			foreach ($what as &$w) {
				$w = self::filter($w, $mode);
			}
		} else {
			switch ($mode) {
				case self::SLASHES:
					return stripslashes($what);

				case self::SLASHES_ADD:
					return addslashes($what);

				case self::HTML:
					return htmlspecialchars($what);

				case self::HTML_DECODE:
					return htmlspecialchars_decode($what);

				case self::TAGS:
					return strip_tags($what);
			}
		}
	}

	/**
	 * Encode and decode data in base64
	 * @param string $string
	 * @param int $action
	 * @param bool $urlSafe
	 * @return string
	 */
	public static function base64 ($string = NULL, $action = self::ENCODE, $urlSafe = false) {
		switch ($action) {
			case self::DECODE:
				return base64_decode(strtr($string, '-_,', '+/='));

			case self::ENCODE:
			default:
				if (!$string) {
					$string  = $_SERVER['REQUEST_URI'];
					$urlSafe = true;
				}
    			return ($urlSafe) ? strtr(base64_encode($string), '+/=', '-_,') : base64_encode($string);
		}
	}

	/**
	 * Is similar to sprintf and vsprintf (http://php.net/sprintf)
	 * @return string
	 */
	public static function format() {
		$args = func_get_args();
		if (func_num_args() > 1) {
			$str = array_shift($args);
			return (is_array($args[0])) ? vsprintf($str, $args[0]) : vsprintf($str, $args);
		} else if (isset($args[0])) {
			return $args[0];
		} else {
			throw new OOException ('Warning: Wrong parameter count for Str::format() in ' . __FILE__ .' on line ' . __LINE__, E_USER_WARNING);
		}
	}

	/**
	 * Return size of string in bytes
	 * @param string $string
	 * @return int
	 */
	public static function size($string) {
		return strlen($string);
	}

	/**
	 * Find substring and return bool, or int information about its position
	 * @param array|string $what
	 * @param string $where
	 * @param int $type
	 * @param bool $caseSensitive
	 * @param int $from
	 * @return bool|int
	 */
	public static function find($what, $where, $type = self::BOOL, $caseSensitive = self::CS, $from = 0) {
		if (is_array($what)) {
			$a = array();

			foreach ($what as $w) {
				$p = self::find($w, $where, $type, $caseSensitive, $from);

				if ($p === false)
					continue;

				if ($type == self::BOOL)
					return true;

				$a[] = $p;
			}

			switch ($type) {
				case self::BOOL:
					return false;

				case self::RIGHT:
					return (empty($a)) ? false : max($a);

				case self::LEFT:
				default:
					return (empty($a)) ? false : min($a);
			}
		}

		if (!is_string($what))
			$what = (string)$what;

		switch ($type) {
			case self::BOOL:
				return ($caseSensitive) ? is_int(mb_strrpos($where, $what, $from)) :
					is_int(mb_strripos($where, $what, $from));

			case self::RIGHT:
				return ($caseSensitive) ? mb_strrpos($where, $what, $from) : mb_strripos(
					$where, $what, $from);

			case self::COUNT:
				return ($caseSensitive) ? mb_substr_count($where, $what) :
					mb_substr_count(mb_strtolower($where), mb_strtolower($what));

			case self::LEFT:
			default:
				return ($caseSensitive) ? mb_strpos($where, $what, $from) : mb_stripos(
					$where, $what, $from);
		}
	}

	/**
	 * Changing case of symbols in string
	 * @param string $string
	 * @param int $what What symbols do you want to change
	 * @param int $mode How you want to change them
	 * @return string
	 */
	public static function changeCase($string, $what = self::ALL, $mode = self::UP)
	{
		switch ($mode) {
			case self::DOWN:
				switch ($what) {
					// all letters down
					case self::ALL:
						return mb_strtolower($string);
					// first letter down
					case self::FIRST:
						return mb_strtolower(mb_substr($string, 0, 1)) . mb_substr($string, 1);
					// each first letter down
					case self::TITLE:
						$pattern = '/(^|[^\p{L}\p{Nd}])([\p{L}\p{Nd}])/ue';
//						$pattern = '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue';
						return preg_replace($pattern, 'mb_strtolower(\'$0\')', $string);
				}

			case self::UP_FORCED:
				switch ($what) {
					// all letters up
					case self::ALL:
						return mb_strtoupper($string);
					// first letter up, all other - down
					case self::FIRST:
						return mb_strtoupper(mb_substr($string, 0, 1)) . mb_strtolower(mb_substr($string, 1));
					// each first letter up, all other - down
					case self::TITLE:
						return mb_convert_case($string, MB_CASE_TITLE);
				}

			case self::DOWN_FORCED:
				switch ($what) {
					// all letters down
					case self::ALL:
						return mb_strtolower($string);
					// first letter down, all other - up
					case self::FIRST:
						return mb_strtolower(mb_substr($string, 0, 1)) . mb_strtoupper(mb_substr($string, 1));
					// each first letters up, other - down
					case self::TITLE:
						return self::caseInvert(mb_convert_case($string, MB_CASE_TITLE));
				}

			case self::INVERT:
				switch ($what) {
					// all letters invert
					case self::ALL:
						return self::caseInvert($string);
					// first letter invert
					case self::FIRST:
						return self::caseInvert(mb_substr($string, 0, 1)) . mb_substr($string, 1);
					// each first letter invert
					case self::TITLE:
						$pattern = '/(^|[^\p{L}\p{Nd}])([\p{L}\p{Nd}])/ue';
//						$pattern = '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue';
						return preg_replace($pattern, 'self::caseInvert(\'$0\')', $string);
				}

			case self::RANDOM:
				switch ($what) {
					// all letters random
					case self::ALL:
						return self::caseRand($string);
					// first letter random
					case self::FIRST:
						return self::caseRand(mb_substr($string, 0, 1)) . mb_substr($string, 1);
					// each first letter random
					case self::TITLE:
						$pattern = '/(^|[^\p{L}\p{Nd}])([\p{L}\p{Nd}])/ue';
//						$pattern = '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue';
						return preg_replace($pattern, 'self::caseRand(\'$0\')', $string);
				}

			case self::UP:
			default:
				switch ($what) {
					// first letter up
					case self::FIRST:
						return mb_strtoupper(mb_substr($string, 0, 1)) . mb_substr($string, 1);
					// each first letter up
					case self::TITLE:
						$pattern = '/(^|[^\p{L}\p{Nd}])([\p{L}\p{Nd}])/ue';
//						$pattern = '/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue';
						return preg_replace($pattern, 'mb_strtoupper(\'$0\')', $string);
					// all letters up
					case self::ALL:
					default:
						return mb_strtoupper($string);
				}
		}
	}

	/* additional functions for changeCase */
	// inverts the case of a string
	private static function caseInvert($string) {
		return preg_replace("/([\p{L}\p{Nd}])/ue", 'self::charCaseInvert(\'$0\')', $string);
	}

	// randomly capilizes/uncaptilizes chars in a string
	private static function caseRand($string) {
		return preg_replace("/([\p{L}\p{Nd}])/ue", 'self::charCaseRand(\'$0\')', $string);
	}

	// invert case for char
	private static function charCaseInvert($char) {
		return ($char == mb_strtolower($char))
			? mb_strtoupper($char) : mb_strtolower($char);
	}

	// set random case for char
	private static function charCaseRand($char) {
		return rand(0, 1) ? self::charCaseInvert($char) : $char;
	}

	/**
	 * Length of string in symbols
	 * @param string $string
	 * @return int
	 */
	public static function length ($string) {
		return mb_strlen($string);
	}

	/**
	  *This function returns a string with whitespace stripped from the beginning and/or end of str .
	 * @param array|string $where
	 * @param string $charlist
	 * @param int $mode
	 * @return array|string
	 */
	public static function trim ($where, $charlist = NULL, $mode = self::BOTH) {
		if (is_array($where)) {
			foreach ($where as &$s)
				$s = self::trim($s, $charlist, $mode);

			return $where;
		}

		if (!$charlist) {
			switch ($mode) {
				case self::LEFT:
					return ltrim($where);

				case self::RIGHT:
					return rtrim($where);

				case self::BOTH:
				default:
					return trim($where);
			}
		}
		switch ($mode) {
			case self::LEFT:
				$charlist = preg_replace('!([\\\\\\-\\]\\[/^])!', '\\\${1}', $charlist);
				return preg_replace('/^[' . $charlist . ']+/u', '', $where);

			case self::RIGHT:
		    	$charlist = preg_replace('!([\\\\\\-\\]\\[/^])!', '\\\${1}', $charlist);
				return preg_replace('/[' . $charlist . ']+$/u', '', $where);

			case self::BOTH:
			default:
				return self::trim(self::trim($where, $charlist, self::LEFT),
					$charlist, self::RIGHT);
		}
	}

	/**
	 * Returns string , reversed.
	 * @param string $string
	 * @param int|null $from
	 * @param int|null $length
	 * @return string
	 */
	public static function reverse($string, $from = NULL, $length = NULL) {
		$matches = array();
		preg_match_all('/./us', $string, $matches);

		$array = $matches[0];
		if (!is_null($from)) {
			if ($length) {
				$a = array_merge(
					array_slice($array, 0, $from),
					array_reverse(array_slice($array, $from, $length)),
					array_slice($array, $from + $length));
			} else {
				$a = array_merge(
					array_slice($array, 0, $from),
					array_reverse(array_slice($array, $from)));
			}
		} else {
			$a = array_reverse($array);
		}

		return implode('', $a);
	}

	/**
	 * Str::fill('abc', 7, 'x') => 'abcxxxx'
	 * @param string $what
	 * @param int $length
	 * @param string $string
	 * @param int $type
	 * @return string
	 */
	public static function fill($what, $length, $string = ' ', $type = self::RIGHT) {
		$ret = '';
		$hn_length_of_padding = $length - mb_strlen($what);
		$hn_psLength = mb_strlen($string); // pad string length

		if ($hn_psLength <= 0 || $hn_length_of_padding <= 0) {
			// padding string equal to 0:
			$ret = $what;
		} else {
			// how many times repeat
			$hn_repeatCount = floor($hn_length_of_padding / $hn_psLength);

			if ($type == self::BOTH) {
				$hs_lastStrLeft = "";
				$hs_lastStrRight = "";
				$hn_repeatCountLeft = $hn_repeatCountRight = ($hn_repeatCount - $hn_repeatCount % 2) / 2;

				// the rest length to pad
				$hs_lastStrLength = $hn_length_of_padding - 2 * $hn_repeatCountLeft * $hn_psLength;
				// the rest length divide to 2 parts
				$hs_lastStrLeftLength = $hs_lastStrRightLength = floor($hs_lastStrLength / 2);
				// the last char add to right side
				$hs_lastStrRightLength += $hs_lastStrLength % 2;

				$hs_lastStrLeft = mb_substr($string, 0, $hs_lastStrLeftLength);
				$hs_lastStrRight = mb_substr($string, 0, $hs_lastStrRightLength);

				$ret = str_repeat($string, $hn_repeatCountLeft) . $hs_lastStrLeft;
				$ret .= $what;
				$ret .= str_repeat($string, $hn_repeatCountRight) . $hs_lastStrRight;
			} else {
				$hs_lastStr = mb_substr($string, 0, $hn_length_of_padding % $hn_psLength); // last part of pad string
				if ($type == self::LEFT)
					$ret = str_repeat($string, $hn_repeatCount) . $hs_lastStr . $what;
				else
					$ret = $what . str_repeat($string, $hn_repeatCount) . $hs_lastStr;
			}
		}
		return $ret;
	}
}